#ifndef unigame_LuaScript_h
#define unigame_LuaScript_h

#include "component/Component.h"
#include <stdarg.h>
#include <map>

struct lua_State;

typedef int (*LuaFunction)(lua_State* ls);

namespace unigame
{
	class ILuaCallback;
	class LuaScript: public Component
	{
	public:
		LuaScript(const char* name);
		~LuaScript();

		void attachScript(const char* name);

		void update();

		void callFunction(const char* name, const char* input, const char* ouput, ...);
		void callFunction(const char* name, const char* input, ...);
		void callFunction(const char* name);

		void registerCallback(ILuaCallback* callback);
		void removeCallback(const char* name);

		void runCallback(const char* name);

		void getMessageParam(const char* format, ...);

		lua_State* state()
		{
			return m_ls;
		}

		void getCallbackName(std::string* out);

	private:
		void callFunction(const char* name, const char* input, const char* ouput, va_list args);
		int32u computeLen(const char* text);

		void makeOutput(int32u len, const char* format, va_list args);

		template<typename T>
		va_list makeInputVector(va_list args);

		template<typename T>
		va_list makeOutputVector(va_list args, int32u sp);

		template<typename T>
		va_list makeInputMap(va_list args);

		template<typename T>
		va_list makeOutputMap(va_list args, int32u sp);

		void getValue(lua_State* ls, int index, int* out);
		void getValue(lua_State* ls, int index, short* out);
		void getValue(lua_State* ls, int index, float* out);
		void getValue(lua_State* ls, int index, double* out);
		void getValue(lua_State* ls, int index, char* out);
		void getValue(lua_State* ls, int index, std::string* out);

		void pushValue(lua_State* ls, int* in);
		void pushValue(lua_State* ls, short* in);
		void pushValue(lua_State* ls, float* in);
		void pushValue(lua_State* ls, double* in);
		void pushValue(lua_State* ls, char* in);
		void pushValue(lua_State* ls, std::string* in);

		void createTable();
		void putInTableVector(int32u tableSp, int32u index);
		void putInTableMap(int32u tableSp);

	private:
		lua_State* m_ls;
		char* m_name;
		std::map<std::string, ILuaCallback*> m_callbacks;
	};

	template<typename T>
	va_list LuaScript::makeInputMap(va_list args)
	{
		std::map<std::string, T>* m = (std::map<std::string, T>*)va_arg(args, void*);
		createTable();
		for(std::map<std::string, T>::iterator it = m->begin(); it != m->end(); ++it)
		{
			std::string str = it->first;
			pushValue(m_ls, &str);
			pushValue(m_ls, &(it->second));
			putInTableMap(-3);
		}
		return args;
	}

	template<typename T>
	va_list LuaScript::makeInputVector(va_list args)
	{
		std::vector<T>* list = va_arg(args, std::vector<T>*);
		int len = list->size();
		createTable();
		for(int32u j = 0; j < len; ++j)
		{
			pushValue(m_ls, &((*list)[j]));
			putInTableVector(-2, j + 1);
		}
		return args;
	}

	template<typename T>
	va_list LuaScript::makeOutputVector(va_list args, int32u sp)
	{
		--sp; 
		std::vector<T>* list = va_arg(args, std::vector<T>*);
		list->clear();
		int32u index = 1;
		lua_pushinteger(m_ls, index);
		lua_gettable(m_ls, sp);
		while(!lua_isnil(m_ls, -1))
		{
			T v;
			getValue(m_ls, -1, &v);
			list->push_back(v);
			++index;
			lua_pop(m_ls, 1);
			lua_pushinteger(m_ls, index);
			lua_gettable(m_ls, sp);
		}
		lua_pop(m_ls, 1);
		return args;
	}

	template<typename T>
	va_list LuaScript::makeOutputMap(va_list args, int32u sp)
	{
		sp -= 1;
		lua_pushnil(m_ls);
		std::map<std::string, T>* m = (std::map<std::string, T>*)va_arg(args, void*);
		m->clear();
		while(lua_next(m_ls, sp))
		{
			std::string key;
			T v;
			getValue(m_ls, -2, &key);
			getValue(m_ls, -1, &v);
			(*m)[key] = v;
			lua_pop(m_ls, 1);
		}
		return args;
	}
}

#endif